/***************************************************************
                       graph.c
written by jhat
 graphing resources

***************************************************************/

#define SEEK_SET 0
#define BUFSZ 120

#include "clam.h"
#include <limits.h>

Graph *_g;
int *_xarray, *_yarray, _xsize, _ysize;
char *_xtitle, *_ytitle, *_fn, *_title;
void (*_pick)(int);

void graphArray(Graph *g, int *xarray, int *yarray, int arraysize,
		char *xtitle, char *ytitle, char *fn, char *title, 
		void (*pick)(int));
void drawXAxis(int *xlabels, int xfrom, int xto);
void drawYAxis(int *ylabels, int yfrom, int yto);

static void graphredraw();
static void GRAPH_Pick(int k, double x, double y);
static void GRAPH_ZoomDown(double x, double y);
static void GRAPH_ZoomDrag(double x, double y);
static void GRAPH_ZoomUp(double x, double y);
static void GRAPH_MoveDown(double x, double y);
static void GRAPH_MoveDrag(double x, double y);
static void GRAPH_MoveUp(double x, double y);
static void drawXorLineInBox(int val);

static int xold, yold, xorig, yorig;
static int xory;
static int zoomormove;
static int ystart, xstart;
static int xborder=100, yborder=100;
static float xscale, yscale;
static int nx, ny;
static int xmin, xmax, ymin, ymax;
static int xpoints, ypoints;
static char graphPointStr[50]="\0";
static int lastBox;
static int charwidth, charheight;

void graphredraw() 
{
  graphArray(_g, _xarray, _yarray, _xsize, _xtitle, _ytitle, _fn, _title,_pick);
}

static void drawXorLineInBox(val)
/* val is the value the line should go through
   xory tells us if it should be horiz or vert
*/
{
  /*graphPoint(xborder+(double)i/xscale, ny-yborder-yarray[i]/yscale); */
  /*graphRectangle(xborder-3, 2, nx-2, ny-yborder+3); */
  if (xory==1) {
    graphXorLine(xstart+val, 2, xstart+val, ny-yborder+3);
  } else {
    graphXorLine(xborder-3, ystart-val, nx-2, ystart-val);
  }
}

void GRAPH_Pick(int k, double x, double y)
{
  k += xmin;
  if ((k==xmin) || k > _xsize) {
    GRAPH_ZoomDown(x+10, y+2); /* don't know why, but the down points have  */
                               /* to be modified to match up with future  */
                               /* drag & up points */
    return;
  }

  if (lastBox != k) {
    sprintf(graphPointStr, "Point:%5d   Value:%5d",
                          k, _yarray[k-1]);

    graphActivate(*_g);

    if (lastBox) {
      /* unhighlight the previous selected point */
      graphBoxDraw(lastBox-xmin, BLACK, WHITE);
    }

    lastBox = k;

    graphBoxDraw(lastBox-xmin, YELLOW, BLACK);
    graphRedraw();

    return;
  }

  /* if we're here, we have double clicked a point.  Call user function */
  if (_pick) _pick(lastBox);
}

static void GRAPH_ZoomDown(double x, double y)
{
  xold = xorig = x; yold = yorig = y;
  xory=0;
  zoomormove = 1;
  xstart = xborder;
  ystart = ny-yborder;

  /* zooming */
  if (x<xborder) {
    xory=2; 
    if (y<(ny-yborder)/2) ystart -= (ypoints-1)/yscale;
  } else if (y>ny-yborder) {
    xory=1; 
    if (x>(nx+xborder)/2) xstart += xpoints/xscale;
  }

  if (xory) {
    graphRegister(LEFT_DRAG, GRAPH_ZoomDrag);
    graphRegister(LEFT_UP, GRAPH_ZoomUp);
    drawXorLineInBox(0);
  }
}

static void GRAPH_ZoomDrag(double x, double y)
{
  drawXorLineInBox((xory==1)?(xold-xorig):(yorig-yold));
  xold = x; yold = y;
  drawXorLineInBox((xory==1)?(xold-xorig):(yorig-yold));

  /*
  graphClear();

  if (xory==1) {
    if (xstart == xborder) drawXAxis(0, xmin + rint((xorig-x)*xscale), xmax);
    else                   drawXAxis(0, xmin, xmax + rint((xorig-x)*xscale));
  } else {
    if (ystart == ny-yborder) drawYAxis(0, ymin + rint((y-yorig)*yscale), ymax);
    else                      drawYAxis(0, ymin, ymax + rint((y-yorig)*yscale));
  }

  graphRedraw();
  */

}

static void GRAPH_ZoomUp(double x, double y)
{
  drawXorLineInBox((xory==1)?(xold-xorig):(yorig-yold));
  graphRegister(LEFT_DRAG, 0);
  graphRegister(LEFT_UP, 0);

  if (xory==1) {
    if (xstart == xborder) xmin += rint((x-xorig)*xscale);
    else                   xmax += rint((x-xorig)*xscale);
  } else {
    if (ystart == ny - yborder) ymin += rint((yorig-y)*yscale);
    else                        ymax += rint((yorig-y)*yscale);
  }

  if (xmin < 0) xmin = 0;
  if (xmax > _xsize) xmax = _xsize;
  if (ymin < 0) ymin = 0;

  graphredraw();
}

static void GRAPH_MoveDown(double x, double y)
{
  xory = 0;
  zoomormove = 2;
  xorig = x; yorig = y;

  if (x < xborder) {
    xory = 2;
  } else if (y > ny-yborder) {
    xory = 1;
  }
  if (xory) {
    graphRegister(MIDDLE_UP, GRAPH_MoveUp);
    graphRegister(MIDDLE_DRAG, GRAPH_MoveDrag);
  }
}

static void GRAPH_MoveDrag(double x, double y)
{
  graphClear();

  if (xory==1)
    drawXAxis(0, xmin - rint((x-xorig)*xscale), xmax - rint((x-xorig)*xscale));
  else
    drawYAxis(0, ymin - rint((yorig-y)*yscale), ymax - rint((yorig-y)*yscale));

  graphRedraw();
}

static void GRAPH_MoveUp(double x, double y)
{
  graphRegister(MIDDLE_UP, 0);
  graphRegister(MIDDLE_DRAG, 0);
  if (xory==1) {
    xmin -= rint((x-xorig)*xscale);
    xmax -= rint((x-xorig)*xscale);
  } else {
    ymin -= rint((yorig-y)*yscale);
    ymax -= rint((yorig-y)*yscale);
  }
  if (xmax > _xsize) {
    xmin -= (xmax-_xsize);
    xmax = _xsize;
  }
  if (xmin < 0) {
    xmax -= xmin;
    xmin = 0;
  }
  if (ymin < 0) {
    ymax -= ymin;
    ymin = 0;
  }

  graphredraw();
}


void graphArray(Graph *g, int *xarray, int *yarray, int arraysize,
		char *xtitle, char *ytitle, char *fn, char *title,
		void (*pick)(int))
/*
  Graph g   - the graph this will be called
  xarray[]  - the x points.  If this is null, we use 1, 2, 3, ..., ysize
  yarray[]  - the y points.
  arraysize - the size of xarray (if non-null) & yarray
  xtitle    - the x axis title
  ytitle    - the y axis title
  fn        - filename for graph print
  title     - title for the graph
  pick      - function for double clicking points
*/
{
  int i;
  float cw, ch;
  int redraw=0;

  zoomormove = 0;

  if (graphActivate(*g)) {
    graphClear();
    redraw=1;
  } else {
    *g = graphCreate(PIXEL_FIT, title, .2, .2, .8, .8);
    graphRegister(RESIZE, graphredraw);
    graphRegister(MIDDLE_DOWN, GRAPH_MoveDown);
    graphRegister(PICK, GRAPH_Pick);
  }
  graphHelp(fn);
  graphTextInfo(&charwidth, &charheight, &cw, &ch);
  graphFitBounds(&nx, &ny);
  nx-=15;
  graphRectangle(xborder-3, 2, nx-2, ny-yborder+3);

  if (!redraw) {
    xmin=0; xmax = arraysize;
    ymin=INT_MAX; ymax = 0;
    for (i=xmin; i<=xmax; i++) {
      if (xmin==0 && yarray[i] > 0) xmin = i;
      if (yarray[i]<ymin) ymin = yarray[i];
      if (yarray[i]>ymax) ymax = yarray[i];
    }
    lastBox = 0;
  }

  printf("Window: X(%d-%d) Y(%d-%d)\n", xmin, xmax, ymin, ymax);


  graphText(fn, 10, ny-yborder+30);

  /* graph the horizontal (x) scale */
  graphText(xtitle, nx/2, ny-yborder+30);
  drawXAxis(0, xmin, xmax);

  /* graph the verticle (y) scale */
  graphText(ytitle, 10, ny/2);
  drawYAxis(0, ymin, ymax);


  graphPointsize(2);
  for (i=xmin; i <= xmax; i++) {
    graphBoxStart();
    if (yarray[i] >= ymin && yarray[i] <= ymax) {
      graphPoint(xborder+(float)(i-xmin)/xscale, 
	         ny-yborder-(yarray[i]-ymin)/yscale);
    }
    graphBoxEnd();
  }

  graphPointStr[0] = '\0';
  graphTextPtr(graphPointStr, nx/2 - 50, ny-20, 40);

  graphRedraw();
  _g = g;
  _xarray = xarray;
  _yarray = yarray;
  _xsize = arraysize;
  _ysize = arraysize;
  _xtitle = xtitle;
  _ytitle = ytitle;
  _fn = fn;
  _title = title;
  _pick = pick;
}


/* starting at 1, intervalmult will generate the sequence */
/* 1, 2, 5,  10, 20, 50,  100, 200, 500,  1000, ... */
float intervalmult[3] = {2, 2.5, 2};

#define LBL_NDX(arr, i) ((arr==0)?i:arr[i])

void drawXAxis(int *xlabels, int xfrom, int xto) {
  int i, anint;
  int xaxis, xaxisminor;
  int largestSize, thisSize;
  char str[8];

  if (xto > _xsize) {
    xfrom -= (xto-_xsize);
    xto = _xsize;
  }

  if (xfrom < 0) {
    if (zoomormove != 1) xto -= xfrom;
    xfrom = 0;
  }

  xpoints = xto-xfrom+1;
  xscale = (float)xpoints/(nx-xborder - 5);

  /* find how much we should increment along the axis labeling */

  largestSize = 0;
  for (i=0; i<xto; i++) {
    thisSize = (log10(LBL_NDX(xlabels, i))+2)*charwidth;
    if (thisSize > largestSize) largestSize = thisSize;
  }

  xaxis = 1;
  xaxisminor = xaxis;
  anint = 0;

  while (xpoints/xaxis > nx/largestSize) {
    if (anint != 1) xaxisminor = xaxis; /* skip the 2's for the minor axis */
    xaxis *= intervalmult[anint++];
    anint %= 3;
  }

  i = ceil((float)xfrom/xaxisminor)*xaxisminor;
  for (; i <= xto; i+= xaxisminor) {
    if (i%xaxis==0) 
      /* this line is the little tick mark for the axis interval */
      graphLine(xborder+(i-xfrom)/xscale, ny-yborder+5,
                xborder+(i-xfrom)/xscale, ny-yborder+11);
    else
      /* this line is the smaller tick mark for the axis minor interval */
      graphLine(xborder+(i-xfrom)/xscale, ny-yborder+5,
                xborder+(i-xfrom)/xscale, ny-yborder+8);
  }

  for (i=xfrom; i <= xto; i+= xaxis) {
    sprintf(str, "%d", LBL_NDX(xlabels, i));
    graphText(str, xborder+(i-xfrom)/xscale-strlen(str)*charwidth/2, 
              ny-yborder+12);

    graphLine(xborder+(i-xfrom)/xscale, ny-yborder+5,
              xborder+(i-xfrom)/xscale, ny-yborder+8);

    /* this ensures we print the top and bottom of the scale */
    if (i == xfrom) i = ceil((float)xfrom/xaxis)*xaxis;
    if (i != xto && i+2*xaxis>xto) i = xto-xaxis;
  }
}

void drawYAxis(int *ylabels, int yfrom, int yto) {
  int i, anint;
  int yaxis, yaxisminor;
  char str[8];

  if (yfrom < 0) {
    if (zoomormove != 1) yto -= yfrom;
    yfrom = 0;
  }

  ypoints = yto-yfrom+1;
  yscale = (float)ypoints/(ny-yborder - 5);

  /* find how much we should increment along the axis labeling */
  yaxis = 1;
  yaxisminor = yaxis;
  anint = 0;
  while (ypoints/yaxis > ny/(1.3*charheight)) {
    if (anint != 1) yaxisminor = yaxis; /* skip the 2's for the minor axis */
    yaxis *= intervalmult[anint++];
    anint %= 3;
  }

  i = ceil((float)yfrom/yaxisminor)*yaxisminor;
  for (; i <= yto; i+= yaxisminor) {
    if (i%yaxis==0) 
      /* this line is the little tick mark for the axis interval */
      graphLine(xborder-9, ny-yborder-(i-yfrom)/yscale,
                xborder-3, ny-yborder-(i-yfrom)/yscale);
    else
      /* this line is the smaller tick mark for the axis minor interval */
      graphLine(xborder-6, ny-yborder-(i-yfrom)/yscale,
                xborder-3, ny-yborder-(i-yfrom)/yscale);
  }

  for (i=yfrom; i <= yto; i+= yaxis) {
    sprintf(str, "%d", LBL_NDX(ylabels, i));
    graphText(str, xborder-11-strlen(str)*charwidth,
              ny-yborder-(i-yfrom)/yscale-charheight/2);

    graphLine(xborder-6, ny-yborder-(i-yfrom)/yscale,
              xborder-3, ny-yborder-(i-yfrom)/yscale);

    /* this ensures we print the top and bottom of the scale */
    if (i == yfrom) i = ceil((float)yfrom/yaxis)*yaxis;
    if (i != yto && i+2*yaxis>yto) i = yto-yaxis;
  }
}
